package com.chess.annotation.demo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewConfiguration;
import android.widget.ImageView;

public class ZoomImageView extends ImageView implements OnTouchListener {
	// TODO looks like it is the standard way to use matrix pair instead of a
	// single one, keep the implementation for the moment
	private Matrix matrix = new Matrix();
	private Matrix savedMatrix = new Matrix();
	@SuppressWarnings("unused")
	private float zoomScale = 1.0f;

	private State mode = State.NONE;

	private PointF startPoint = new PointF();
	private PointF zoomMidPoint = new PointF();

	private float oldZoomDist = 0;
	private boolean initLayout = true;
	private Bitmap bitmap = null;

	// if the image is very small, we allow it to shrink to its original size;
	// otherwise, we allow it to shrink to fit to screen
	private float minScale = 1.0f;
	// TODO need to consider the extreme case for a very large image
	private float maxScale = 4.0f;
	private boolean enableMiniScale = true;
	public float initScale;
	// invoke zoom/scroll only when we detect large enough movement
	private int mTouchSlop;
	private OnTouchListener onTouchListener;
	private postZoomedImageActionDelegate delegate = null;

	public ZoomImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public ZoomImageView(Context context) {
		super(context);
		init();
	}

	public ZoomImageView(Context context, boolean miniScaleEnabled) {
		super(context);
		this.enableMiniScale = miniScaleEnabled;
		init();
	}

	private void init() {
		super.setClickable(true);
		setScaleType(ScaleType.MATRIX);
		final ViewConfiguration configuration = ViewConfiguration.get(getContext());
		mTouchSlop = configuration.getScaledTouchSlop();
		setOnTouchListener(this);
	}

	private void postZoomedImageAction() {
		if (delegate != null) {
			delegate.postZoomedImageAction();
		}
	}

	private void preZoomedImageAction() {
		if (delegate != null) {
			delegate.preZoomedImageAction();
		}
	}

	public interface postZoomedImageActionDelegate {
		public void postZoomedImageAction();

		public void preZoomedImageAction();
	}

	public void setOnTouchListenerEnabled(boolean isEnabled) {
		if (isEnabled) {
			setOnTouchListener(onTouchListener);
		} else {
			setOnTouchListener(null);
		}
	}

	/**
	 * @param isEnabled
	 */
	public void setUsingMinimumScale(boolean isEnabled) {
		enableMiniScale = isEnabled;
	}

	private void adjustImageScale(Matrix matrix, float midx, float midy) {
		float[] values = new float[9];
		matrix.getValues(values);

		float unadjustedScale = values[Matrix.MSCALE_X];

		float scale = 1;
		if (unadjustedScale >= initScale * maxScale) {
			scale = initScale * maxScale / unadjustedScale;
		} else if (enableMiniScale && unadjustedScale <= initScale * minScale) {
			scale = initScale * minScale / unadjustedScale;
		}
		matrix.postScale(scale, scale, midx, midy);
	}

	private void adjustImagePosition(Matrix matrix) {
		float[] values = new float[9];
		matrix.getValues(values);

		float scale;
		float offsetX, offsetY;

		int displayWidth = getWidth();
		int displayHeight = getHeight();

		int bitmapWidth = displayWidth;
		int bitmapHeight = displayHeight;
		if (bitmap != null) {
			bitmapWidth = bitmap.getWidth();
			bitmapHeight = bitmap.getHeight();
		}
		scale = values[Matrix.MSCALE_X];

		float imageWidth = bitmapWidth * scale;
		float imageHeight = bitmapHeight * scale;

		offsetX = values[Matrix.MTRANS_X];
		offsetY = values[Matrix.MTRANS_Y];

		// TODO redundant code, clean up later when the logics are formalized
		if (displayWidth >= imageWidth) {
			float makeupX = offsetX - (displayWidth - imageWidth) / 2;
			matrix.postTranslate(-makeupX, 0);
		} else if (displayWidth - offsetX >= imageWidth) {
			float makeupX = offsetX - (displayWidth - imageWidth);
			matrix.postTranslate(-makeupX, 0);
		} else if (offsetX >= 0) {
			matrix.postTranslate(-offsetX, 0);
		}

		if (displayHeight >= imageHeight) {
			float makeupY = offsetY - (displayHeight - imageHeight) / 2;
			matrix.postTranslate(0, -makeupY);
		} else if (displayHeight - offsetY >= imageHeight) {
			float makeupY = offsetY - (displayHeight - imageHeight);
			matrix.postTranslate(0, -makeupY);
		} else if (offsetY >= 0) {
			matrix.postTranslate(0, -offsetY);
		}
	}

	public boolean hitLeftBound() {
		Matrix matrix = getImageMatrix();
		float[] values = new float[9];
		matrix.getValues(values);

		float offsetX = values[Matrix.MTRANS_X];
		if (offsetX >= 0)
			return true;
		else
			return false;
	}

	public boolean hitRightBound() {
		Matrix matrix = getImageMatrix();
		float[] values = new float[9];
		matrix.getValues(values);

		int displayWidth = getWidth();
		float scale = values[Matrix.MSCALE_X];
		float imageWidth = displayWidth;
		if (bitmap != null)
			imageWidth = bitmap.getWidth() * scale;

		float offsetX = values[Matrix.MTRANS_X];

		if (displayWidth - offsetX >= imageWidth)
			return true;
		else
			return false;
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		if (initLayout && bitmap != null) {
			initLayout = false;
			resetLayout();
		}
	}

	public void resetView() {
		matrix = new Matrix();
		savedMatrix = new Matrix();
		mode = State.NONE;
		startPoint = new PointF();
		zoomMidPoint = new PointF();
		oldZoomDist = 0;

		if (bitmap != null)
			resetLayout();
	}

	private void resetLayout() {

		int displayWidth = getMeasuredWidth();
		int displayHeight = getMeasuredHeight();

		int bitmapWidth = bitmap.getWidth();
		int bitmapHeight = bitmap.getHeight();

		float scaleX = (float)displayWidth / (float)bitmapWidth;
		float scaleY = (float)displayHeight / (float)bitmapHeight;

		minScale = Math.min(Math.min(scaleX, scaleY), 1);

		initScale = Math.min(scaleX, scaleY);

		savedMatrix.set(matrix);
		matrix.set(savedMatrix);
		matrix.postScale(initScale, initScale, zoomMidPoint.x, zoomMidPoint.y);
		setImageMatrix(matrix);

		float extraSpaceX = (float)displayWidth - (initScale * (float)bitmapWidth);
		float extraSpaceY = (float)displayHeight - (initScale * (float)bitmapHeight);

		extraSpaceY /= (float)2;
		extraSpaceX /= (float)2;

		savedMatrix.set(matrix);
		matrix.set(savedMatrix);
		matrix.postTranslate(extraSpaceX, extraSpaceY);
		setImageMatrix(matrix);
		setBackgroundColor(Color.TRANSPARENT);
	}

	@Override
	public void setImageBitmap(Bitmap bm) {
		super.setImageBitmap(bm);
		if (bitmap != null) {
			bitmap = bm;
			resetView();
		} else {
			bitmap = bm;
		}
	}

	private float getZoomDistance(MotionEvent event) {
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return (float)Math.sqrt(x * x + y * y);
	}

	private PointF getMidPoint(MotionEvent event) {
		float x = event.getX(0) + event.getX(1);
		float y = event.getY(0) + event.getY(1);
		PointF point = new PointF();
		point.set(x / 2, y / 2);

		int displayWidth = getWidth();
		int displayHeight = getHeight();

		Matrix matrix = getImageMatrix();
		float[] values = new float[9];
		matrix.getValues(values);

		float scale = values[Matrix.MSCALE_X];
		float imageWidth = displayWidth;
		float imageHeight = displayHeight;
		if (bitmap != null) {
			imageWidth = bitmap.getWidth() * scale;
			imageHeight = bitmap.getHeight() * scale;
		}

		if (imageWidth < displayWidth) {
			point.x = displayWidth / 2;
		}

		if (imageHeight < displayHeight) {
			point.y = displayHeight / 2;
		}

		return point;
	}

	private float getDistance(float x1, float y1, float x2, float y2) {
		float dx = x1 - x2;
		float dy = y1 - y2;
		return (float)Math.sqrt(dx * dx + dy * dy);
	}

	public boolean isZooming() {
		return mode == State.ZOOM;
	}

	public void setDelegate(postZoomedImageActionDelegate delegate) {
		this.delegate = delegate;
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// TODO Auto-generated method stub
		boolean respondIntercept = false;
		setImageMatrix(matrix);

		switch (event.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
			preZoomedImageAction();
			savedMatrix.set(matrix);
			zoomScale = initScale;
			startPoint.set(event.getX(), event.getY());
			mode = State.DRAG;
			respondIntercept = true;
			break;
		case MotionEvent.ACTION_POINTER_DOWN:
			oldZoomDist = getZoomDistance(event);
			if (oldZoomDist > 10f) {
				savedMatrix.set(matrix);
				float[] values = new float[9];
				matrix.getValues(values);
				zoomScale = values[Matrix.MSCALE_X];
				mode = State.ZOOM;
			}
			respondIntercept = false;
			break;
		case MotionEvent.ACTION_UP:
			adjustImagePosition(matrix);
			mode = State.NONE;
			respondIntercept = true;
			postZoomedImageAction();
			break;
		case MotionEvent.ACTION_POINTER_UP:
			adjustImageScale(matrix, zoomMidPoint.x, zoomMidPoint.y);
			mode = State.NONE;
			respondIntercept = false;
			break;
		case MotionEvent.ACTION_CANCEL:
			adjustImageScale(matrix, zoomMidPoint.x, zoomMidPoint.y);
			adjustImagePosition(matrix);
			mode = State.NONE;
			respondIntercept = true;
			break;
		case MotionEvent.ACTION_MOVE:
			if (mode == State.DRAG) {
				if (getDistance(event.getX(), event.getY(), startPoint.x, startPoint.y) > mTouchSlop) {
					matrix.set(savedMatrix);
					matrix.postTranslate(event.getX() - startPoint.x, event.getY() - startPoint.y);
					adjustImagePosition(matrix);
				}
				respondIntercept = true;
			} else if (mode == State.ZOOM) {
				float newZoomDistance = getZoomDistance(event);

				if (newZoomDistance > mTouchSlop) {
					zoomMidPoint = getMidPoint(event);

					matrix.set(savedMatrix);
					float scale = newZoomDistance / oldZoomDist;

					float[] values = new float[9];
					matrix.getValues(values);
					float scale0 = values[Matrix.MSCALE_X];

					matrix.postScale(scale, scale, zoomMidPoint.x, zoomMidPoint.y);

					float[] values1 = new float[9];
					matrix.getValues(values1);
					float scale1 = values1[Matrix.MSCALE_X];

					adjustImageScale(matrix, zoomMidPoint.x, zoomMidPoint.y);

					float[] values2 = new float[9];
					matrix.getValues(values2);
					float scale2 = values2[Matrix.MSCALE_X];

					android.util.Log.d("temp", "initScale :" + scale0 + ", distanceScale:" + scale + ", unadjustScale:"
							+ scale1 + ", adjusted scale:" + scale2);

					adjustImagePosition(matrix);

				}
				respondIntercept = false;
			}
			break;
		}
		setImageMatrix(matrix);
		return !respondIntercept;

	}

}